package cn.skywalks.common.utils.pager;

import cn.skywalks.common.utils.context.ContextUtil;
import cn.skywalks.common.utils.reflect.ReflectUtil;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * mybatis拦截器插件
 * 使用时
 * 1，在mybatis全局配置文件中加载拦截器，并且配置上拦截关键字pageSqlId（正则）
 * 2，在web.xml中配置PagerFilter过滤所有的后台请求
 * 3，前台传值：将pageSize和currentPage作为请求参数
 *
 * Created by lpeng on 2017/2/24.
 */
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
)})
public class PagerInterceptor implements Interceptor {

    private static Logger logger = Logger.getLogger(PagerInterceptor.class);

    private String pageSqlId;

    public Object intercept(Invocation invocation) throws Throwable {
        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
        BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getFileValue(handler, "delegate");
        MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFileValue(delegate, "mappedStatement");
        Pager pager = ContextUtil.getPager();
        BoundSql boundSql = delegate.getBoundSql();
        if (mappedStatement.getId().matches(pageSqlId) && null != pager) {//分页参数不为空，分页关键字匹配成功
            int countRow = 0;
            String countSql = this.getCountSql(boundSql.getSql());
            Connection connection = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            this.setParameters(preparedStatement, mappedStatement, countBS, boundSql.getParameterObject(), boundSql);
            ResultSet rs = preparedStatement.executeQuery();
            if(rs.next()) {
                countRow = rs.getInt(1);
            }
            rs.close();
            preparedStatement.close();
            pager.setTotal(countRow);
            ContextUtil.setRequestAttribute("pager", pager);
            //原sql替换成分页sql
            ReflectUtil.setFileValue(boundSql, "sql", this.generatePagerSql(boundSql.getSql(), pager));
        }
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        this.pageSqlId = properties.getProperty("pageSqlId");
        logger.debug(" the matching suffix : 【" + this.pageSqlId + "】");
    }

    /**
     * 生成分页sql
     * @param sql
     * @param pager
     * @return
     */
    private String generatePagerSql(String sql, Pager pager){
        if(pager != null) {
            StringBuffer pageSql = new StringBuffer();
            pageSql.append(sql);
            pageSql.append(" limit " + pager.getStart() + "," + pager.getPageSize());
            return pageSql.toString();
        } else {
            return sql;
        }
    }

    /**
     * 拼接select count(*),获取查询总条数的sql
     *
     * @param sql
     * @return
     * @throws Exception
     */
    private String getCountSql(String sql) throws Exception {
        String countSql = null;
        sql = sql.replaceAll("\n|\t|\r", " ");
        Matcher matcher = Pattern.compile("\\s+from\\s+.+", Pattern.CASE_INSENSITIVE).matcher(sql);
        if (matcher.find()) {
            String subSql = matcher.group();
            matcher = Pattern.compile("\\s+order+\\s+by\\s+", Pattern.CASE_INSENSITIVE).matcher(subSql);
            if (matcher.find()) {
                subSql = subSql.substring(0, subSql.indexOf(matcher.group()));
            }
            countSql = "select count(*) " + subSql;
            return countSql;
        } else {
            throw new Exception("无效的查询语句【" + sql + "】");
        }
    }

    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject, BoundSql oBoundSql) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            Configuration configuration = mappedStatement.getConfiguration();
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);

            for (int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping) parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    Object oBoundSqlAddParameter = oBoundSql.getAdditionalParameter(propertyName);
                    if (oBoundSqlAddParameter != null) {
                        boundSql.setAdditionalParameter(propertyName, oBoundSqlAddParameter);
                    }

                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);
                    Object value;
                    if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (propertyName.startsWith("__frch_") && boundSql.hasAdditionalParameter(prop.getName())) {
                        value = boundSql.getAdditionalParameter(prop.getName());
                        if (value != null) {
                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
                        }
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    if (typeHandler == null) {
                        throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
                    }

                    typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
                }
            }
        }

    }
}
